/*
 * @(#)SeedGenerator.java	1.32 06/10/10
 *
 * Copyright  1990-2008 Sun Microsystems, Inc. All Rights Reserved.  
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER  
 *   
 * This program is free software; you can redistribute it and/or  
 * modify it under the terms of the GNU General Public License version  
 * 2 only, as published by the Free Software Foundation.   
 *   
 * This program is distributed in the hope that it will be useful, but  
 * WITHOUT ANY WARRANTY; without even the implied warranty of  
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU  
 * General Public License version 2 for more details (a copy is  
 * included at /legal/license.txt).   
 *   
 * You should have received a copy of the GNU General Public License  
 * version 2 along with this work; if not, write to the Free Software  
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  
 * 02110-1301 USA   
 *   
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa  
 * Clara, CA 95054 or visit www.sun.com if you need additional  
 * information or have any questions. 
 *
 */

package sun.security.provider;

/**
 * <P> This class generates seeds for the cryptographically strong random
 * number generator.
 * <P> The seed is produced using one of two techniques, via a computation
 * of current system activity or from an entropy gathering device.
 * <p> In the default technique the seed is  produced by counting the
 * number of times the VM manages to loop in a given period. This number
 * roughly reflects the machine load at that point in time.
 * The samples are translated using a permutation (s-box)
 * and then XORed together. This process is non linear and
 * should prevent the samples from "averaging out". The s-box
 * was designed to have even statistical distribution; it's specific
 * values are not crucial for the security of the seed.
 * We also create a number of sleeper threads which add entropy
 * to the system by keeping the scheduler busy.
 * Twenty such samples should give us roughly 160 bits of randomness.
 * <P> These values are gathered in the background by a daemon thread
 * thus allowing the system to continue performing it's different
 * activites, which in turn add entropy to the random seed.
 * <p> The class also gathers miscellaneous system information, some
 * machine dependent, some not. This information is then hashed together
 * with the 20 seed bytes.
 * <P> The alternative to the above approach is to acquire seed material
 * from an entropy gathering device, such as /dev/random. This can be
 * accomplished by setting the value of the "securerandom.source"
 * security property (in the Java security properties file) to a URL
 * specifying the location of the entropy gathering device.
 * In the event the specified URL cannot be accessed the default
 * mechanism is used.
 * The Java security properties file is located in the file named
 * &lt;JAVA_HOME&gt;/lib/security/java.security, where &lt;JAVA_HOME&gt;
 * refers to the directory where the SDK was installed.
 *
 * @version 1.22, 05/17/00
 * @author Joshua Bloch
 * @author Gadi Guy
 */

import java.security.*;
import java.io.*;
import java.util.Properties;
import java.util.Enumeration;
import java.net.*;
import sun.security.util.Debug;

abstract class SeedGenerator {

    // Static instance is created at link time
    private static SeedGenerator instance;

    private static final Debug debug = Debug.getInstance("provider");

    private final static String PROP_EGD = "java.security.egd";
    private final static String PROP_RNDSOURCE = "securerandom.source";

    final static String URL_DEV_RANDOM = "file:/dev/random";

    // Static initializer to hook in selected or best performing generator
    static {
	String egdSource = (String)java.security.AccessController.doPrivileged
		(new java.security.PrivilegedAction() {
		    public Object run() {
			String egdSource = System.getProperty(PROP_EGD, "");
			if (egdSource.length() != 0) {
			    return egdSource;
		        }
			egdSource = Security.getProperty(PROP_RNDSOURCE);
			if (egdSource == null) {
			    return "";
			}
			return egdSource;
		    }
		});

	// Try the URL specifying the source
	// e.g. file:/dev/random
	//
	// The URL file:/dev/random is used to indicate the SeedGenerator
	// using OS support, if available.
	// On Windows, the causes MS CryptoAPI to be used.
	// On Solaris, this is the identical to using
	// URLSeedGenerator to read from /dev/random. For Linux,
        // CDC/FP has implemented a fix to handle reads on /dev/random
        // hanging on some devices, and we use NativeURLSeedGenerator
        // (CR 6260366).

	if (egdSource.equals(URL_DEV_RANDOM)) {
	    try {
	        instance = new NativeSeedGenerator();
		if (debug != null) {
		    debug.println("Using operating system seed generator");
		}
	    } catch (IOException e) {
		if (debug != null) {
		    debug.println("Failed to use operating system seed "
				  + "generator: " + e.toString());
	        }
	    }
	} else if (egdSource.length() != 0) {
	    try {
		instance = new URLSeedGenerator(egdSource);
		if (debug != null) {
		    debug.println("Using URL seed generator reading from "
		    		  + egdSource);
		}
	    } catch (IOException e) {
		if (debug != null)
		    debug.println("Failed to create seed generator with "
				  + egdSource + ": " + e.toString());
	    }
	}

	// Fall back to ThreadedSeedGenerator
	if (instance == null) {
	    if (debug != null) {
	        debug.println("Using default threaded seed generator");
	    }
	    instance = new ThreadedSeedGenerator();
	}
    }

    /**
     * Fill result with bytes from the queue. Wait for it if it isn't ready.
     */
    static public void generateSeed(byte[] result) {
	instance.getSeedBytes(result);
    }

    void getSeedBytes(byte[] result) {
	for (int i = 0; i < result.length; i++) {
	  result[i] = getSeedByte();
	}
    }

    abstract byte getSeedByte();

    /**
     * Retrieve some system information, hashed.
     */
    static byte[] getSystemEntropy() {
	byte[] ba;
	final MessageDigest md;

	try {
    	    md = MessageDigest.getInstance("SHA");
    	} catch (NoSuchAlgorithmException nsae) {
    	    throw new InternalError("internal error: SHA-1 not available.");
	}

	// The current time in millis
	byte b =(byte)System.currentTimeMillis();
	md.update(b);

	java.security.AccessController.doPrivileged
	    (new java.security.PrivilegedAction() {
		public Object run() {

		    try {
			// System properties can change from machine to machine
			String s;
			Properties p = System.getProperties();
			Enumeration e = p.propertyNames();
			while (e.hasMoreElements()) {
			    s =(String)e.nextElement();
			    md.update(s.getBytes());
			    md.update(p.getProperty(s).getBytes());
			}

			md.update
			    (InetAddress.getLocalHost().toString().getBytes());

			// The temporary dir
			File f = new File(p.getProperty("java.io.tmpdir"));
			String[] sa = f.list();
			for(int i = 0; i < sa.length; i++)
			    md.update(sa[i].getBytes());

		    } catch (Exception ex) {
			md.update((byte)ex.hashCode());
		    }

		    // get Runtime memory stats
		    Runtime rt = Runtime.getRuntime();
		    byte[] memBytes = longToByteArray(rt.totalMemory());
		    md.update(memBytes, 0, memBytes.length);
		    memBytes = longToByteArray(rt.freeMemory());
		    md.update(memBytes, 0, memBytes.length);

		    return null;
		}
	    });
	return md.digest();
    }

    /**
     * Helper function to convert a long into a byte array (least significant
     * byte first).
     */
    private static byte[] longToByteArray(long l) {
	byte[] retVal = new byte[8];

	for (int i=0; i<8; i++) {
	    retVal[i] = (byte) l;
	    l >>= 8;
	}

	return retVal;
    }

    /*
    // This method helps the test utility receive unprocessed seed bytes.
    public static int genTestSeed() {
	return myself.getByte();
    }
    */


    private static class ThreadedSeedGenerator extends SeedGenerator implements Runnable {
	// Queue is used to collect seed bytes
	private byte[] pool;
	private int start, end, count;

	// Thread group for our threads
	ThreadGroup seedGroup;

	/**
         * The constructor is only called once to construct the one
         * instance we actually use. It instantiates the message digest
         * and starts the thread going.
         */

	ThreadedSeedGenerator() {
	    pool = new byte[20];
	    start = end = 0;

	    MessageDigest digest;

	    try {
		digest = MessageDigest.getInstance("SHA");
	    } catch (NoSuchAlgorithmException e) {
		throw new InternalError("internal error: SHA-1 not available.");
	    }

	    final ThreadGroup[] finalsg = new ThreadGroup[1];
	    Thread t = (Thread)java.security.AccessController.doPrivileged
		(new java.security.PrivilegedAction() {
			public Object run() {
			    ThreadGroup parent, group =
				Thread.currentThread().getThreadGroup();
			    while ((parent = group.getParent()) != null)
				group = parent;
			    finalsg[0] = new ThreadGroup
				(group, "SeedGenerator ThreadGroup");
			    Thread newT = new Thread(finalsg[0],
						     ThreadedSeedGenerator.this,
						     "SeedGenerator Thread");
			    newT.setPriority(Thread.MIN_PRIORITY);
			    newT.setDaemon(true);
			    return newT;
			}
		    });
	    seedGroup = finalsg[0];
	    t.start();
	}

	/**
	 * This method does the actual work. It collects random bytes and
	 * pushes them into the queue.
	 */
	final public void run() {
	    try {
		while (true) {
		    // Queue full? Wait till there's room.
		    synchronized(this) {
			while (count >= pool.length)
			    wait();
		    }

		    int counter, quanta;
		    byte v = 0;

		    // Spin count must not be under 64000
		    for (counter = quanta = 0; (counter < 64000) && (quanta < 6);
			 quanta++) {

			// Start some noisy threads
			try {
			    BogusThread bt = new BogusThread();
			    Thread t = new Thread
				(seedGroup, bt, "SeedGenerator Thread");
			    t.start();
			} catch (Exception e) {
			    throw new InternalError("internal error: " +
						    "SeedGenerator thread creation error.");
			}

			// We wait 250milli quanta, so the minimum wait time
			// cannot be under 250milli.
			int latch = 0;
			latch = 0;
			long l = System.currentTimeMillis() + 250;
			while (System.currentTimeMillis() < l) {
			    synchronized(this){};
			    latch++;
			}

			// Translate the value using the permutation, and xor
			// it with previous values gathered.
			v ^= rndTab[latch % 255];
			counter += latch;
		    }

		    // Push it into the queue and notify anybody who might
		    // be waiting for it.
		    synchronized(this) {
			pool[end] = v;
			end++;
			count++;
			if (end >= pool.length)
			    end = 0;

			notifyAll();
		    }
		}
	    } catch (Exception e) {
		if (e instanceof InterruptedException &&
		    sun.misc.ThreadRegistry.exitRequested())
		{
		    return;
		}
		throw new InternalError("internal error: " +
					"SeedGenerator thread generated an exception.");
	    }
	}

	byte getSeedByte() {
	    byte b = 0;

	    try {
		// Wait for it...
		synchronized(this) {
		    while (count <= 0)
			wait();
		}
	    } catch (Exception e) {
		if (count <= 0)
		    throw new InternalError("internal error: " +
					    "SeedGenerator thread generated an exception.");
	    }

	    synchronized(this) {
		// Get it from the queue
		b = pool[start];
		pool[start] = 0;
		start++;
		count--;
		if (start == pool.length)
		    start = 0;

		// Notify the daemon thread, just in case it is
		// waiting for us to make room in the queue.
		notifyAll();
	    }

	    return b;
	}

	// The permutation was calculated by generating 64k of random
	// data and using it to mix the trivial permutation.
	// It should be evenly distributed. The specific values
	// are not crucial to the security of this class.
	private static byte[] rndTab = {
	    56, 30, -107, -6, -86, 25, -83, 75, -12, -64,
	    5, -128, 78, 21, 16, 32, 70, -81, 37, -51,
	    -43, -46, -108, 87, 29, 17, -55, 22, -11, -111,
	    -115, 84, -100, 108, -45, -15, -98, 72, -33, -28,
	    31, -52, -37, -117, -97, -27, 93, -123, 47, 126,
	    -80, -62, -93, -79, 61, -96, -65, -5, -47, -119,
	    14, 89, 81, -118, -88, 20, 67, -126, -113, 60,
	    -102, 55, 110, 28, 85, 121, 122, -58, 2, 45,
	    43, 24, -9, 103, -13, 102, -68, -54, -101, -104,
	    19, 13, -39, -26, -103, 62, 77, 51, 44, 111,
	    73, 18, -127, -82, 4, -30, 11, -99, -74, 40,
	    -89, 42, -76, -77, -94, -35, -69, 35, 120, 76,
	    33, -73, -7, 82, -25, -10, 88, 125, -112, 58,
	    83, 95, 6, 10, 98, -34, 80, 15, -91, 86,
	    -19, 52, -17, 117, 49, -63, 118, -90, 36, -116,
	    -40, -71, 97, -53, -109, -85, 109, -16, -3, 104,
	    -95, 68, 54, 34, 26, 114, -1, 106, -121, 3,
	    66, 0, 100, -84, 57, 107, 119, -42, 112, -61,
	    1, 48, 38, 12, -56, -57, 39, -106, -72, 41,
	    7, 71, -29, -59, -8, -38, 79, -31, 124, -124,
	    8, 91, 116, 99, -4, 9, -36, -78, 63, -49,
	    -67, -87, 59, 101, -32, 92, 94, 53, -41, 115,
	    -66, -70, -122, 50, -50, -22, -20, -18, -21, 23,
	    -2, -48, 96, 65, -105, 123, -14, -110, 69, -24,
	    -120, -75, 74, 127, -60, 113, 90, -114, 105, 46,
	    27, -125, -23, -44, 64
	};

	/**
	 * This inner thread causes the thread scheduler to become 'noisy',
	 * thus adding entropy to the system load.
	 * At least one instance of this class is generated for every seed byte.
	 */

	private class BogusThread implements Runnable {
	    final public void run() {
		try {
		    for(int i = 0; i < 5; i++)
			Thread.sleep(50);
		    // System.gc();
		} catch (Exception e) {
		}
	    }
	}
    }

    static class URLSeedGenerator extends SeedGenerator {

	private String deviceName;
	private BufferedInputStream devRandom;


	/**
	 * The constructor is only called once to construct the one
	 * instance we actually use. It opens the entropy gathering device
	 * which will supply the randomness.
	 */

	URLSeedGenerator(String egdurl) throws IOException {
	    if (egdurl == null) {
		throw new IOException("No random source specified");
	    }
	    deviceName = egdurl;
	    init();
	}

	URLSeedGenerator() throws IOException {
	    this(SeedGenerator.URL_DEV_RANDOM);
	}

	private void init() throws IOException {
	    final URL device = new URL(deviceName);
	    devRandom =
		(BufferedInputStream)java.security.AccessController.doPrivileged
		(new java.security.PrivilegedAction() {
			public Object run() {
			    try {
				return new BufferedInputStream(device.openStream());
			    } catch (IOException ioe) {
				return null;
			    }
			}
		    });

	    if (devRandom == null) {
		throw new IOException("failed to open " + device);
	    }
	}

	byte getSeedByte() {
	    byte b[] = new byte[1];
	    int stat;
	    try {
		stat = devRandom.read(b, 0, b.length);
	    } catch (IOException ioe) {
		throw new InternalError("URLSeedGenerator " + deviceName +
					" generated exception: " +
					ioe.getMessage());
	    }
	    if (stat == b.length) {
		return b[0];
	    } else if (stat == -1) {
		throw new InternalError("URLSeedGenerator " + deviceName +
					   " reached end of file");
	    } else {
		throw new InternalError("URLSeedGenerator " + deviceName +
					   " failed read");
	    }
	}

    }

}
